package NarkyPackage;
import javax.swing.JPanel;
import java.awt.event.KeyEvent;
import java.awt.Graphics;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Timer;
import java.util.TimerTask;
import java.awt.Color;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

/**
 * Cada instancia de esta clase representa a un juego en particular. Los juegos son 
 * de objetos de tipo ElementoDeNarkyKong. El juego ademas se encarga de 
 * informar a los elementos del juego sobre las configuraciones que el usuario escogio.
 * 
 * @author GameTECH
 * @version 1.0
 *
 */

public class Juego extends JPanel implements Runnable {

	private ElementoDeNarkyKong[] elementos;
	private Timer timer;
	private boolean continua = true;
	public NarkyApplet applet;
	public DetectorDeTeclas teclado;
	private Juego juego;
	private boolean congelar = false;
	private boolean pintando = false;
	public ServidorDeMapa mapa;
	private Jugador ganador = null;
	private int estadoDelJuego;
	private int contadorTeclado = 5;
	private boolean tecladoBloqueado = false;
	private int score;
	private Jugador jugador;
	private int nivel =3;
	private ConfiguracionDeTeclas conf;
	private double poderDeBala;
	public boolean debugScreen = false;
	private boolean pausaDibujada = false;

	public static final long ActualizacionesPorSegundo = 15;
	public static final long serialVersionUID = 3;

	public static final int JUGANDO = 0;
	public static final int GANADO = 1;
	public static final int PERDIDO = -1;
	public static final int PAUSADO = 2;

	/**
	 * Crea un juego nuevo utilizando la configuracion de teclas especificada
	 * para el applet de narky kong especificada en 'applet'
	 * 
	 * @param jugador    Configuracion de teclas para el jugador.
	 * @param applet     NarkyApplet al que el juego pertenece.
	 * @param dificultad La dificultad del juego
	 */
	public Juego(ConfiguracionDeTeclas jugador, NarkyApplet applet, int dificultad) {
		super();
		this.setSize(NarkyApplet.APP_WIDTH, NarkyApplet.APP_HEIGHT);
		this.applet = applet;
		juego = this;
		conf = jugador;
		poderDeBala =dificultad*dificultad*.1;
		elementos = null;
		mapa = new ServidorDeMapa(this, nivel);
		this.jugador = new Jugador(jugador, this, "Jugador 1", poderDeBala);
		teclado = new DetectorDeTeclas();
		applet.addKeyListener(teclado);

		estadoDelJuego = Juego.JUGANDO;

		score = 0;

		ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
		threadExecutor.execute(this);


		iniciarActualizacionesDelJuego();

	}

	/**
	 * Quita a todo lo relacionado con el nivel anterior del juego y
	 * se encarga de cargar todo lo necesario para el siguiente nivel
	 */
	public void siguienteNivel() {
		applet.sonido.play("siguienteNivel");
		congelar = true;
		double vidaJugador = 1 - ((1 - jugador.getValorVida())/2);
		elementos = null;
		nivel++;
		mapa = new ServidorDeMapa(this, nivel);
		jugador = new Jugador (conf, this, "Jugador 1", poderDeBala);
		jugador.setVida(vidaJugador);
		congelar = false;
		estadoDelJuego = Juego.JUGANDO;
	}

	/**
	 * Manda a actualizar al juego, detecta que teclas estan siendo presionadas
	 * si es pausa o alguna otra tecla del sistema entonces reacciona. Despues
	 * manda a cada uno de los elementos del juego a actualizarse
	 */
	public void actualizar() {

		if (teclado.estaPresionada(KeyEvent.VK_Q)) {
			applet.juegoTerminado();
		} else if (teclado.estaPresionada(KeyEvent.VK_P)) {
			if (!tecladoBloqueado) {
				if (estadoDelJuego == Juego.JUGANDO)
					estadoDelJuego = Juego.PAUSADO;
				else if (estadoDelJuego == Juego.PAUSADO){
					pausaDibujada = false;
					estadoDelJuego = Juego.JUGANDO;
				}

				contadorTeclado = 5;
				tecladoBloqueado = true;
			}
		}

		if (tecladoBloqueado){
			contadorTeclado--;
			if (contadorTeclado < 0)
				tecladoBloqueado = false;
		}

		if (estadoDelJuego == Juego.JUGANDO) {
			//Actualizar todos los elementos
			for (int i = 0; i<elementos.length; i++) {
				elementos[i].actualizar();
			}
		} if (estadoDelJuego == Juego.GANADO &&
				teclado.estaPresionada(KeyEvent.VK_ENTER)) {
			juego.siguienteNivel();
		}
	}

	/**
	 * Metodo que crea un timer con el task de mandar a llamar al metodo
	 * actualizar() del juego el numero de veces por segundo especificado
	 * en la constante de Juego 'ActualizacionesPorSegundo'
	 */
	private void iniciarActualizacionesDelJuego() {
		TimerTask actualizar = new TimerTask() {
			public void run() {
				juego.actualizar();
			}
		};

		timer = new Timer();
		timer.schedule(actualizar, 1000/Juego.ActualizacionesPorSegundo,
				1000/Juego.ActualizacionesPorSegundo);
	}

	/**
	 * Anade a la coleccion de objetos del juego el elemento que se le esta pasando como
	 * parametro.
	 * 
	 * @param elemento El elemento del juego que se desea agregar.
	 */
	public void registrarElemento(ElementoDeNarkyKong elemento){
		// Nota: Redisenar este metodo urgentemente...
		// no se debe llamar desde el constructor, pues asume
		// algun elemento dentro del arreglo
		if (elementos == null) {
			elementos = new ElementoDeNarkyKong[1];
			elementos[0] = elemento;
			return;
		}

		ElementoDeNarkyKong[] viejo = elementos;
		elementos = new ElementoDeNarkyKong[viejo.length + 1];
		for (int i = 0; i< viejo.length; i++) {
			elementos[i] = viejo[i];
		}
		elementos [elementos.length - 1] = elemento;
		viejo = null;


	}

	/**
	 * Quita de la coleccion de objetos del juego el elemento que se le esta pasando
	 * como parametro. Si el elemento  que se quiere remover no existe el metodo no hace nada.
	 * 
	 * @param elemento
	 */
	public void removerElemento(ElementoDeNarkyKong elemento) {
		while (pintando);

		congelar = true;
		if (elementos.length == 1) {
			elementos = null;
		}

		boolean hayMatch = false;
		for (int i = 0; i < elementos.length; i++) {
			if (elementos[i].equals(elemento))
				hayMatch = true;
		}

		if (hayMatch) {
			ElementoDeNarkyKong[] viejo = elementos;
			elementos = new ElementoDeNarkyKong[viejo.length -1];

			int j = 0;
			for (int i = 0; i < viejo.length; i++) {
				if (!viejo[i].equals(elemento)){
					elementos[j] = viejo[i];
					j++;
				}
			}
		}

		congelar = false;
	}

	/**
	 * Utilizando una estrategia de Double Buffer, dibuja primero el menu del juego, de
	 * ahi manda a llamar el metodo dibujar(Graphics g) de cada uno de los elementos que
	 * estan contenidos en el juego pasandoles la instancia de Graphics.
	 * 
	 * @param g La instancia de Graphics para dibujar
	 */
	public void paint(Graphics g){
		if (congelar)
			return;

		pintando = true;

		((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		Font font30 = applet.narkyFont.deriveFont(Font.PLAIN, 30);


		if (estadoDelJuego == Juego.JUGANDO) {
			mapa.dibujar(g);
			for (int i = 0; i< elementos.length; i++) {
				elementos[i].dibujar(g);
			}
			g.setFont( applet.narkyFont.deriveFont(Font.PLAIN, 15));
			g.setColor(new Color (255,0,0,150));
			g.fillRoundRect(240, -20, 150, 50, 40, 40);
			g.setColor(Color.WHITE);
			g.drawString("Puntos: " +score, 310, 20);
			g.drawString("Vida: " + jugador.getVida(), 250, 20);
		} else if (estadoDelJuego == Juego.GANADO) {
			g.setFont(font30);
			g.setColor(Color.RED);
			g.drawString("Pasaste el nivel", 180, 290);
			g.drawString(ganador.name(), 250, 330);

			g.setColor(Color.BLUE);
			g.drawString("Presiona 'Q' para salir...", 80, 530);
			g.drawString("Presiona 'Enter' para continuar", 80,560);
		} else if (estadoDelJuego == Juego.PERDIDO){
			g.setFont(font30);
			g.setColor(Color.RED);
			g.drawString("Perdiste!!!", 230, 290);

			g.setColor(Color.BLUE);
			g.drawString("Presiona 'Q' para salir...", 80, 550);
			congelar = true;
		}else if (estadoDelJuego == Juego.PAUSADO) {
			if (!pausaDibujada){
				g.setColor(new Color(0,0,0,100));
				g.fillRoundRect(80, 250, 455, 100, 60, 60);

				g.setFont( font30);
				g.setColor(Color.RED);
				g.drawString("Juego pausado", 230, 290);
				g.setFont( applet.narkyFont.deriveFont(Font.PLAIN, 20));
				g.drawString("Presiona 'P' para reanudar o 'Q' para salir", 130, 330);
				pausaDibujada = true;
			}
		}


		pintando = false;
	}


	/**
	 * El metodo que maneja el segundo hilo del juego, el encargado de dibujarse.
	 * El metodo se cicla alternando entre repaint() y sleep(). 
	 */
	public void run() {
		while (continua) {
			repaint();
			//System.out.println("x");
		}
	}

	/**
	 * El metodo que detiene al juego, para que el Applet pueda recuperar la
	 * pantalla y no se queden hilos corriendo.
	 */
	public void detener() {
		continua = false;
		timer.cancel();
	}

	public ElementoDeNarkyKong hayAlguienMas(ElementoDeNarkyKong e) {
		ElementoDeNarkyKong objeto = null;
		Rectangle eRect = e.getRect();
		for (int i = 0; i < elementos.length; i++) {
			if (elementos[i].ocupaEstaPosicion(eRect))
				if (!e.equals(elementos[i]))
					objeto = elementos[i];
		}
		return objeto;
	}

	/**
	 * Permite a las balas hacerle da�o a otras monos regresando
	 * la instancia del mono al que estan tocando si es que estan
	 * tocando a alguno
	 * 
	 * @param rect Rectangulo con la posicion de la bala
	 * @return     ElementoDeNarkyKong al que tocan
	 */
	public ElementoDeNarkyKong hayAlguienMas(Rectangle rect) {
		ElementoDeNarkyKong objeto = null;
		for (int i = 0; i < elementos.length; i++) {
			if (elementos[i].ocupaEstaPosicion(rect))
				if (!elementos[i].tipoDeElemento().equals("Bala"))
					objeto = elementos[i];
		}
		return objeto;
	}

	public void gane(Jugador j) {
		applet.sonido.play("siguienteNivel");
		ganador = j;
		estadoDelJuego = Juego.GANADO;
	}

	public void perdi(Jugador j) {
		applet.sonido.play("muerto");
		estadoDelJuego = Juego.PERDIDO;
	}

	/**
	 * Sube puntos cuando el jugador mata a algo
	 * @param puntos
	 */
	public void addScore(int puntos) {
		score += puntos;
	}

	public boolean isCongelar() {
		return congelar;
	}

	public void setCongelar(boolean congelar) {
		this.congelar = congelar;
	}

	/**
	 * Permite al Juego pedir que se detenga todo cuando ya
	 * paso todos los niveles el jugador
	 */
	public void termine() {
		detener();
		applet.gane();
	}

}
